#include "Specimen.hpp"
#include "EvolutionaryException.hpp"
#include <cstdlib>
#include <cfloat>
#include <cstdio>
#include <ctime>

using std::vector;

///\brief Flag signalising if random number generator has been turned on 
static bool srandRunning=false;

///\brief Function which should generate uniform random values from all 
/// coordinate space
Gene generateRandom(const Gene &minX,
		    const Gene &maxX)
{
  //  srand(time(0));
  Gene result=static_cast<Gene>(rand())/RAND_MAX;//random number from 0 to 1
  result*=maxX-minX; //scaling to assigned range
  result+=minX;//shifting to assigned range
  return result;//*((rand()%2==1)?-1:1);//assigning sign
}

Specimen::Specimen():_dimensions(0),_fitness(0),_genes(NULL)
{
  if(!srandRunning)
    {
      srand(time(0));
      srandRunning=true;
    }
}

Specimen::Specimen(const Specimen &argument)
{
  _dimensions=argument._dimensions;
  _fitness=argument._fitness;
  _genes = new Gene[_dimensions];
  for(unsigned i=0;i<_dimensions;++i)
    {
      _genes[i]=argument[i];
    }
  }

Specimen &Specimen::operator=(const Specimen &argument)
{
  _dimensions=argument._dimensions;
  _fitness=argument._fitness;
  if(_genes)
    {
      delete [] _genes;
    }
  _genes = new Gene[_dimensions];
  for(unsigned i=0;i<_dimensions;++i)
    {
      _genes[i]=argument[i];
    }
  return *this;
}


void Specimen::init(int dimensions)
{
  if(dimensions>0)
    {
      _dimensions=dimensions;
      if(_genes)
	{
	  delete [] _genes;
	}
      _genes = new Gene[_dimensions];
    }
  else
    {
      EvolutionaryException ex(0,"There has to be at least one dimension",
			       __func__,
			       __FILE__,__LINE__);
      throw ex;
    }
}

void Specimen::initRandom(int dimensions,const vector<Gene> &minX,
			  const vector<Gene> &maxX)
{
  if(dimensions<=0)
    {
      EvolutionaryException ex(0,"There has to be at least one dimension",__func__,
			   __FILE__,__LINE__);
      throw ex;
    }

  _dimensions=dimensions;
  if(_genes)
    {
      delete [] _genes;
    }
  _genes = new Gene[_dimensions];

  for(unsigned i=0;i<_dimensions;++i)
    {
      _genes[i]=generateRandom(minX[i],maxX[i]);
    }
}

void Specimen::singlePointCrossover(const Specimen &parent,
			  Specimen &childA,Specimen &childB) const
{
  unsigned cuttingPoint=rand()%_dimensions;
  childA.init(_dimensions);
  childB.init(_dimensions);
  
  {
    unsigned i;
    for(i=0;i<cuttingPoint;++i)
      {
	childA[i]=_genes[i];
	childB[i]=parent[i];
      }
    for(;i<_dimensions;++i)
      {
	childB[i]=_genes[i];
	childA[i]=parent[i];
      }
  }
}

void Specimen::multiPointCrossover(const Specimen &parent,
			 Specimen &childA,Specimen &childB) const
{
  childA.init(_dimensions);
  childB.init(_dimensions);
  
  for(unsigned i=0;i<_dimensions;++i){
    if(rand()%2==0)
      {
	childA[i]=_genes[i];
	childB[i]=parent[i];
      }
    else
      {
	childB[i]=_genes[i];
	childA[i]=parent[i];
      }
  }
}

void Specimen::algebraicRandCrossover(const Specimen &parent,
				     Specimen &childA,Specimen &childB) const
{
 long double proportion=rand()/RAND_MAX;
 algebraicDetCrossover(parent,childA,childB,proportion);
}

void Specimen::algebraicDetCrossover(const Specimen &parent,
				     Specimen &childA,Specimen &childB,
				     const double& proportion) const
{
  if(parent._dimensions!=_dimensions)
    {
      EvolutionaryException ex(0,"dimensions do not agree",
			       __func__,__FILE__,__LINE__);
      throw ex;
    }

  childA.init(_dimensions);
  childB.init(_dimensions);
  
  for(unsigned i=0;i<_dimensions;++i)
    {
      childA[i]=proportion*_genes[i]+(1-proportion)*parent[i];
      childB[i]=(1-proportion)*_genes[i]+proportion*parent[i];
    }
}

Specimen Specimen::uniformMutation(const std::vector<Gene> &minX,
				   const std::vector<Gene> &maxX)const
{
  Specimen spec;

  spec=*this;
  
  unsigned mutatedGene=rand()%_dimensions;
  spec[mutatedGene]=generateRandom(minX[mutatedGene],maxX[mutatedGene]);
 return spec;
}

Gene &Specimen::operator[](const size_t& index)
{
  if(index<0 || index >= _dimensions)
    {
      EvolutionaryException ex(0,"bad index",__func__,__FILE__,__LINE__);
      throw ex;
    }
  return _genes[index];
}

const Gene &Specimen::operator[](const size_t &index)const
{
  if(index<0 || index >= _dimensions)
    {
      EvolutionaryException ex(0,"bad index",__func__,__FILE__,__LINE__);
      throw ex;
    }
  return _genes[index];
}

double Specimen::measureDistance(const Specimen &arg)const
{
  if(_dimensions==arg._dimensions)
    {
      double distance=0;
      for(unsigned i=0;i<_dimensions;++i)
	{
	  distance+=pow(arg._genes[i]-_genes[i],2);
	}
      distance=sqrt(distance);
      return distance;
    }
  else
    {
      EvolutionaryException ex(0,"dimensions do not agree",
			       __func__,__FILE__,__LINE__);
      throw ex;
    }
}

Specimen::~Specimen()
{
  if(_genes)
    {
      delete [] _genes;
    }
}



